package com.ihr360.job.core.repeat.context;

import com.ihr360.job.core.repeat.RepeatContext;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class RepeatContextSupport  extends SynchronizedAttributeAccessor implements RepeatContext {
    private RepeatContext parent;

    private int count;

    private volatile boolean completeOnly;

    private volatile boolean terminateOnly;

    private Map<String, Set<Runnable>> callbacks = new HashMap<String, Set<Runnable>>();

    /**
     * Constructor for {@link RepeatContextSupport}. The parent can be null, but
     * should be set to the enclosing repeat context if there is one, e.g. if
     * this context is an inner loop.
     * @param parent
     */
    public RepeatContextSupport(RepeatContext parent) {
        super();
        this.parent = parent;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.batch.repeat.RepeatContext#isCompleteOnly()
     */
    @Override
    public boolean isCompleteOnly() {
        return completeOnly;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.batch.repeat.RepeatContext#setCompleteOnly()
     */
    @Override
    public void setCompleteOnly() {
        completeOnly = true;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.batch.repeat.RepeatContext#isTerminateOnly()
     */
    @Override
    public boolean isTerminateOnly() {
        return terminateOnly;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.batch.repeat.RepeatContext#setTerminateOnly()
     */
    @Override
    public void setTerminateOnly() {
        terminateOnly = true;
        setCompleteOnly();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.batch.repeat.RepeatContext#getParent()
     */
    @Override
    public RepeatContext getParent() {
        return parent;
    }

    /**
     * Used by clients to increment the started count.
     */
    public synchronized void increment() {
        count++;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.batch.repeat.RepeatContext#getStartedCount()
     */
    @Override
    public synchronized int getStartedCount() {
        return count;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.springframework.batch.repeat.RepeatContext#registerDestructionCallback
     * (java.lang.String, java.lang.Runnable)
     */
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        synchronized (callbacks) {
            Set<Runnable> set = callbacks.get(name);
            if (set == null) {
                set = new HashSet<Runnable>();
                callbacks.put(name, set);
            }
            set.add(callback);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.batch.repeat.RepeatContext#close()
     */
    @Override
    public void close() {

        List<RuntimeException> errors = new ArrayList<RuntimeException>();

        Set<Map.Entry<String, Set<Runnable>>> copy;

        synchronized (callbacks) {
            copy = new HashSet<Map.Entry<String, Set<Runnable>>>(callbacks.entrySet());
        }

        for (Map.Entry<String, Set<Runnable>> entry : copy) {

            for (Runnable callback : entry.getValue()) {
				/*
				 * Potentially we could check here if there is an attribute with
				 * the given name - if it has been removed, maybe the callback
				 * is invalid. On the other hand it is less surprising for the
				 * callback register if it is always executed.
				 */
                if (callback != null) {
					/*
					 * The documentation of the interface says that these
					 * callbacks must not throw exceptions, but we don't trust
					 * them necessarily...
					 */
                    try {
                        callback.run();
                    }
                    catch (RuntimeException t) {
                        errors.add(t);
                    }
                }
            }
        }

        if (errors.isEmpty()) {
            return;
        }

        throw errors.get(0);
    }
}